﻿using System;
using System.Collections.Generic;


namespace Logging
{
		/// <summary>
	/// N.B. This Logging class is not designed for a multi threaded environment
	/// </summary>
	public class Logger : IDisposable {
		
		/// <summary>
		/// static storage for the singleton
		/// </summary>
		private static Logger mLogger;

		/// <summary>
		/// instance accessor to the singleton
		/// </summary>
		public static Logger Instance {
			get {
				if (Logger.mLogger == null)
					Logger.mLogger = new Logger();
				return mLogger;
			}
		}

		/// <summary>
		/// static Access to the primary Log stream - for backwards compatability
		/// </summary>
		public static ILoggable Log {
			get{
				return Logger.GetLog(0);
			}
		}

		/// <summary>
		/// static retrieval method allowing multiple streams to be accessed - falls back to default stream if can't find it.
		/// </summary>
		/// <param name="streamIndex">stream to access</param>
		/// <returns></returns>
		public static ILoggable GetLog(int streamIndex = 0) {		
			MultiLogger aLogger;
			if (!Logger.Instance.mLogStreams.TryGetValue(streamIndex, out aLogger)) {
				aLogger = Logger.Instance.mLogStreams[0];
				aLogger.WriteStampedLine("*** Error in logger setup - recieved request for log stream \"{0}\" which is not instantiated - logging to default stream instead ***", streamIndex); 
			}
			return aLogger;
		}

		/// <summary>
		/// Use to add log streams to a given index;
		/// N.B. once added you cannot remove log streams, and calling dispose on them is a very bad idea outside of the dispose on this whole logger.
		/// Doing so can result in "alreadydisposed" exceptions bringing down the whole application.
		/// </summary>
		/// <param name="anIndex">index of stream to set</param>
		/// <param name="aLogger">a Logger to set it to</param>
		/// <param name="alsoToConsole">the alsoToConsole parameter adds console logging to the index as well as the supplied logger, default = false</param>
		public static void AddLogStream(int anIndex, ILoggable aLogger, bool alsoToConsole = false)
		{
			if (Logger.Instance.mLogStreams.ContainsKey(anIndex)) {
				Logger.Instance.mLogStreams[anIndex].AddLogger(aLogger);
			}
			else {
				Logger.Instance.mLogStreams.Add(anIndex, new MultiLogger(aLogger));
			}
			if(alsoToConsole) Logger.Instance.mLogStreams[anIndex].AddLogger(new TWLogger(Console.Out));
		}

		/// <summary>
		/// used to add a new logstream to a currently unusedIndex
		/// </summary>
		/// <param name="aLogger">the Logger to add to the new index</param>
		/// <param name="alsoToConsole">the alsoToConsole parameter adds console logging to the index as well as the supplied logger, default = false</param>
		/// <returns>the index of the new stream</returns>
		public static int AddNewLogStream(ILoggable aLogger, bool alsoToConsole = false) {
			int index = 0;
			while(Logger.Instance.mLogStreams.ContainsKey(index)) index++;
			Logger.AddLogStream(index, aLogger, alsoToConsole);
			return index;
		}

		//Instance fields and methods

		/// <summary>
		/// Dictionary of loggers against indices for stream access
		/// </summary>
		private Dictionary<int, MultiLogger> mLogStreams = new Dictionary<int, MultiLogger>(5);

		/// <summary>
		/// private constructor - only ever used by the Instance accessor
		/// </summary>
		private Logger() {
			MultiLogger defaultLogger = new MultiLogger(new TWLogger(Console.Out), new FileLogger("notes"));
			mLogStreams.Add(0, defaultLogger);
		}

		#region IDisposable Members

		public void  Dispose()
		{
			List<ILoggable> loggersToDispose = new List<ILoggable>();
			foreach (ILoggable logger in Logger.Instance.mLogStreams.Values) {
				logger.Flush();
				logger.Dispose();
			}
		}
		#endregion
	}
}
